﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Mail;
using System.Net.Mime;
using System.Runtime.Serialization;
using K3R.Core;
using System.Collections;
using System.Linq;

namespace K3R.Email {
	public delegate void EmailSentEventHandler(IEmailMessage email);
	public delegate void EmailNotSentEventHandler(IEmailMessage email);

	[DataContract]
	public class EmailQueue<TEmail> : List<IEmailMessage> where TEmail: IEmailMessage, new() {
		#region Protected Members
		protected SmtpClient _smtp = null;
		#endregion

		#region Properties
		[DataMember(Name = "running")] public bool Running { get; private set; }
		#endregion

		#region Events
		public event EmailSentEventHandler EmailSent;
		public event EmailNotSentEventHandler EmailNotSent;
		#endregion

		#region Constructors
		public EmailQueue()
			: base() {
			_smtp = new SmtpClient();
			_smtp.SendCompleted += new SendCompletedEventHandler(smtp_SendCompleted);

			Running = false;
		}
		#endregion

		#region Instance
		public void Refresh() {
			if (!Running) {
				Running = true;
				this.Clear();
				this.AddRange(Load());
				Running = false;
			}
		}
		#endregion

		#region Run
		public void Run(bool refresh, bool async) {
			if (refresh)
				Refresh();

			if (!Running) {
				Running = true;

				Dictionary<int, EmailTemplate> cache = new Dictionary<int, EmailTemplate>();
				foreach (IEmailMessage email in this) {
					if (!Running)
						break;

					EmailTemplate template;
					if (!cache.TryGetValue(email.Type, out template)) {
						cache.Add(email.Type, EmailTemplate.Load(email.Type));
						template = cache[email.Type];
					}

					string subject = template.Title;
					string body = email.TextReplace(template.Body);

					MailMessage message = new MailMessage { Subject = subject };
					message.To.Add(email.Address);

					if (template.IsHtml)
						message.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(body, new ContentType("text/html")));
					else message.Body = body;

					if (async)
						SendAsync(message, email);
					else Send(message, email);
				}
				Running = false;
			}
		}

		public void Run(bool async) {
			Run(true, async);
		}

		public void Run() {
			Run(false);
		}

		public void CancelRun() {
			_smtp.SendAsyncCancel();
			Running = false;
		}
		#endregion

		#region Send
		protected void Send(MailMessage message, IEmailMessage email) {
			email.MarkAsSending();

			try {
				_smtp.Send(message);
				email.MarkAsSent();

				if (EmailSent != null)
					EmailSent(email);
			}
			catch {
				email.MarkAsNotSending();

				if (EmailNotSent != null)
					EmailNotSent(email);
			}
		}

		protected void SendAsync(MailMessage message, IEmailMessage email) {
			email.MarkAsSending();
			_smtp.SendAsync(message, email);
		}

		protected void smtp_SendCompleted(object sender, AsyncCompletedEventArgs e) {
			IEmailMessage email = (IEmailMessage)e.UserState;

			if (e.Cancelled) {
				email.MarkAsCancelled();

				if (EmailNotSent != null)
					EmailNotSent(email);
			}
			else if (e.Error != null) {
				email.MarkAsNotSending();

				if (EmailNotSent != null)
					EmailNotSent(email);
			}
			else {
				email.MarkAsSent();

				if (EmailSent != null)
					EmailSent(email);
			}
		}
		#endregion

		#region Static
		public IEnumerable<IEmailMessage> Load() {
			return new TEmail().List();
		}
		#endregion
	}
}
